#!/bin/python
from PyQt5 import QtCore, QtWidgets
import PyQt5
from PyQt5.QtCore import QTimer, Qt
import Ui_MainWindow#@NOTE:继承父类
import sSerial
import threading
import platform#@NOTE:用于判断处于哪个操作系统的库
import pickle#@NOTE:用于储存设置项数据的模块
import os
from datetime import datetime
import MyUi
#@NOTE:多线程回收所需要的模块
import inspect
import ctypes
import subprocess

#@FUNC:创建一个继承自QtWidgets.QDialog()的子类，并重写closeEvent函数，为了使用关闭程序时的消息对话框真的是不计成本了……
class Dialog(QtWidgets.QDialog):
    ConfigWindow=None
    #@NOTE:用于接入带UI类Dialog
    def sConfigColse(self,WindowHaveUI):
        self.ConfigWindow=WindowHaveUI
    #@NOTE:重写closeEvent函数，注意既然是重写说明该函数是QtWidgets.QDialog()类中固定存在的用于接收关闭事件的函数；
    """ 
    @TAG重点：
    a.为什么在本项目中，不可以通过重写UI的closeEvent函数来实现接收关闭事件？
    1.很抱歉的告诉你，本UI中并没有继承QtWidgets.QDialog()类，所以无法直接调用。
    2.即便是继承了，你也完成了closeEvent函数的重写，关闭窗口时我们是对窗口对象进行的关闭操作，而非对UI进行操作，故UI的closeEvent是无法接收到窗口的关闭事件的；
    3.在尝试使用MyUi类继承并重写closeEvent时，因为同时继承了两个父类导致通过self来调用对象时出现了大量的调用异常（未深究请务必不要这么干！）； """

    def closeEvent(self,event):
        #@NOTE:方案1
        Box=QtWidgets.QMessageBox()
        # Shi=Box.addButton(Box.tr("是"),Box.YesRole)#@FIXME:放弃汉化，代码量过大判断源于方案2链接2
        # Box.addButton(Box.tr("否"),Box.NoRole)
        reply = Box.question(
            self,                                   #@NOTE:第一个参数是父控件指针
            '【提示】',                              #@NOTE:第二个参数是标题
            '确认退出？退出前请注意数据保存！',        #@NOTE:第三个参数是内容
            Box.Yes | Box.No,                       #@NOTE:第四个参数是窗口里面要多少个按钮（默认为OK）
            Box.Yes                                 #@NOTE:第五个参数指定按下Enter时使用的按钮。（默认为NoButton，此时QMessageBox会自动选择合适的默认值。）
            )
        
        if reply == Box.Yes:
            self.ConfigWindow.sButton_CloseCom()
            self.ConfigWindow.sSaveBrowserToLog_Close()
            self.ConfigWindow.sSaveCMDToLog()
            event.accept()
        else:
            event.ignore()
        """ 
        #@NOTE:方案2
        # 1.https://www.cnblogs.com/crwy/p/9599361.html
        # 2.https://baijiahao.baidu.com/s?id=1663571669213473885&wfr=spider&for=pc
        Box=QtWidgets.QMessageBox(QtWidgets.QMessageBox.question(),"[提示]{}","确认退出？退出前请注意数据保存！",QtWidgets.QMessageBox.NoButton,self)
        Shi=Box.addButton(Box.tr("是"),Box.YesRole)
        Box.addButton(Box.tr("否"),Box.NoRole)
        Box.exec_()
        if Box.clickedButton() == Shi:
            event.accept()
        else:
            event.ignore() """

#@FUNC:自定义Qt信号类，用于子线程通知主线程更新界面使用
class Signal(QtCore.QObject):
    signal=QtCore.pyqtSignal(object)#@NOTE:创建一个信号对象
    """ 
    #@TAG:Qt中信号用法的说明：
    1.信号对象在功能上实际上是槽（即此处的“signal”实际上是连接功能函数的槽，一旦接收到对应的信号“.emit()”就会执行自己所连接的功能函数）；
    2.所以综上自定义信号的用法就是，创建一个信号对象，将其与要操作的功能函数连接（connect），然后根据自己的需要构建信号的信号发射函数（即此处的“def sSend_Signal(self,Data)函数”），需要执行功能函数时通过调用“def sSend_Signal(self,Data)函数”即可实现操作； """
    def __init__(self):
        super().__init__()

    #@NOTE:自行书写的信号发射函数，信号发射时顺便还能携带一个变量
    def sSend_Signal(self,Data):
        self.signal.emit(Data)

#@FUNC:继承于Qt界面（Qt设计师自动生成的类作为父类，避免了每次修改界面都要重新编写代码）的子类
class MyUi(Ui_MainWindow.Ui_Form):
    Receiv_Signal=Signal()#@NOTE:自定义消息接收显示更新信号
    Com=sSerial.Serial_Info()
    Timer=QTimer()#@TAG重点#@NOTE:用于控制定时发送的定时器对象
    Timer_AUTOCheckConnectCOM=QTimer()#@NOTE:用于自动扫描已连接串口的定时器
    Timer_DisplayNoEnterMsn=QTimer()#@NOTE:用于控制接收消息显示更新速率的定时器
    
    ComList=Com.LookSerial()#@NOTE:扫描串口

    #@NOTE:初始化清空的全局变量
    serial=None#@NOTE:存储选定串口的变量
    Enter=None#@NOTE:储存回车形式的变量（n or nr）
    Receiv=None#@NOTE:储存接收的信息的变量
    StopRW=0#@NOTE:用于控制自动接收消息的标志位
    Main_UI=None#@NOTE:用于存储界面对象的变量
    SaveLog_Index=0#@NOTE:用于控制是否保存日志文件的标志位变量
    CloseSaveLogSW_Index=2#@NOTE:用于控制工具关闭日志保存方式的标志位
    ChildThread_Receiv=None#@NOTE:自动接收消息的子线程所用到变量
    FirstOpenCom_index=1#@NOTE:首次打开串口标志位
    #@NOTE:路径变量
    Path_Program=os.getcwd()
    if platform.system()=="Windows":
        Path_LogDir=Path_Program+"\\PBComLog"
    elif platform.system()=="Linux":
        Path_LogDir=Path_Program+"/PBComLog"

    Path_CloseLog=Path_LogDir+"/PBCom_RECCloseLog.txt"
    Path_EmptyLog=Path_LogDir+"/PBCom_RECEmptyLog.txt"
    try:
        os.mkdir(Path_LogDir)
    except:
        print("[SUDO]:路径已存在")
    DateTime_Str=""#@NOTE:用于存储日志栏中日期时间的变量
    RefreshReceiveMsn_Index=1#@NOTE:消息接收标志位
    ComOpening_Index=0#@NOTE:串口状态标志位，1打开，0关闭

    

    Log=datetime.now().strftime("【打开工具时间】%Y年%m月%d日-%H:%M:%S\n")#@NOTE:用于保存日志的变量
    Empty_Index=0#@NOTE:清空按钮标志位

    #@NOTE:初始化标志位变量
    AtEndTheAutomaticPressEnter=0#@NOTE:末尾自动添加回车
    DisplaySend=1#@NOTE:显示发送信息
    ReceiveAutoEnter_SW=1#@NOTE:接收消息自动换行
    DisplayEnterInfo_SW=1#@NOTE:显示回车信息
    Baud="2400"
    SelectNR_SW=1#@NOTE:回车键NR选择标志位  1==\N\R 0==\N
    AUTOSend_SW=0#@NOTE:自动发送标志位
    AUTOSendTime=1000#@NOTE:自动发送频率
    SystemTime_Str=datetime.now().strftime('%Y%m%d')#@NOTE:当前的系统时间
    SystemTime_Str_Old=None#@NOTE:用于储存上次使用工具的日期
    LogSN=0#@NOTE:当天日志文件的编号
    DisplayDate=0#@NOTE:显示日期
    DisplayTime=0#@NOTE:显示时间
    SendTextData=""#@NOTE:用于保存发送区数据的变量
    DisplayNoEnterMSN_Index=0#@NOTE:显示接收无回车消息标志位
    ReceiveRefreshRate=100#@NOTE:消息接收刷新率


    SettingConfig_Dict={
            "EndAutoAddEnterIndex":AtEndTheAutomaticPressEnter,
            "DisplaySendIndex":DisplaySend,
            "ReceiveAutoEnterIndex":ReceiveAutoEnter_SW,
            "DisplayEnterInfoIndex":DisplayEnterInfo_SW,
            "Baud":Baud,
            "SelectNRIndex":SelectNR_SW,
            "AUTOSendIndex":AUTOSend_SW,
            "AUTOSendTime":AUTOSendTime,
            "SystemTime":SystemTime_Str,
            "LogSerialNumber":LogSN,
            "DisplayDate":DisplayDate,
            "DisplayTime":DisplayTime,
            "SendTextData":SendTextData,
            "DisplayNoEnterMSN_Index":DisplayNoEnterMSN_Index,
            "ReceiveRefreshRate":ReceiveRefreshRate,
            }#@NOTE:用于保存各项设置数据的字典
    try:
        #@NOTE:加载Pickle中的配置数据
        SettingConfig_Pickle_FD=open("PBCom_SettingConfig.pickle",'r+b')#@NOTE:打开用于储存标志位变量的文件
        SettingConfig_Pickle_Dict=pickle.load(SettingConfig_Pickle_FD)#@NOTE:加载Pickel数据到临时变量Dict中
        SettingConfig_Pickle_FD.close()#@NOTE:写入完成关闭文件
    except:
        SettingConfig_Pickle_Dict=None
    SettingConfig_Pickle_FD=None
    try:
            #@NOTE:加载配置文件中的数据用于初始化标志位变量
            AtEndTheAutomaticPressEnter=SettingConfig_Pickle_Dict["EndAutoAddEnterIndex"]#@NOTE:末尾自动添加回车
            DisplaySend=SettingConfig_Pickle_Dict["DisplaySendIndex"]#@NOTE:显示发送信息
            ReceiveAutoEnter_SW=SettingConfig_Pickle_Dict["ReceiveAutoEnterIndex"]#@NOTE:接收消息自动换行
            DisplayEnterInfo_SW=SettingConfig_Pickle_Dict["DisplayEnterInfoIndex"]#@NOTE:显示回车信息
            Baud=SettingConfig_Pickle_Dict["Baud"]
            SelectNR_SW=SettingConfig_Pickle_Dict["SelectNRIndex"]#@NOTE:回车键NR选择标志位  1==\N\R 0==\N
            AUTOSendTime=SettingConfig_Pickle_Dict["AUTOSendTime"]
            SystemTime_Str_Old=SettingConfig_Pickle_Dict["SystemTime"]
            if SystemTime_Str_Old==SystemTime_Str:
                LogSN=SettingConfig_Pickle_Dict["LogSerialNumber"]
            else:
                LogSN=0
            DisplayDate=SettingConfig_Pickle_Dict["DisplayDate"]
            DisplayTime=SettingConfig_Pickle_Dict["DisplayTime"]
            SendTextData=SettingConfig_Pickle_Dict["SendTextData"]
            DisplayNoEnterMSN_Index=SettingConfig_Pickle_Dict["DisplayNoEnterMSN_Index"]
            ReceiveRefreshRate=SettingConfig_Pickle_Dict["ReceiveRefreshRate"]
            
            SettingConfig_Pickle_Dict=None#@NOTE:清空读取字典
    except:
        SettingConfig_Pickle_Dict=None#@NOTE:清空读取字典
        try:
            os.remove("PBCom_SettingConfig.pickle")
        except:
            print("[SUDO]:未找到配置文件，即将重新创建！")
        SettingConfig_Pickle_FD=open("PBCom_SettingConfig.pickle",'w+b')#@NOTE:打开用于储存标志位变量的文件
        
        #@NOTE:将已经字典化的初始化变量数据写入Pickle文件中
        pickle.dump(
            SettingConfig_Dict
            ,SettingConfig_Pickle_FD
        )
        SettingConfig_Pickle_FD.close()#@NOTE:写入完成关闭文件
        SettingConfig_Pickle_FD=None
    #@NOTE:介绍页
    DirectionsForUse_Str="============================【提示】============================\n1.欢迎使用本工具，为了数据安全PBCom采用了三种数据保留方式。其中两种为自动保存，属于Recovery数据，第三种则是通过保存按钮手动保存。三种日志均保存在:{}下,您可以自行查阅，希望对您有所帮助！以下为三种日志的详细说明：\n【=================={}_PBCom_Log_{}.txt==================】\n通过保存按钮保存当前日志框所显示的所有数据，序号表示这是当前日期内的第几份日志；\n【===================PBCom_RECEmptyLog.txt ===================】\n保存着上次日志框所清除的所有数据（下次清除时将自动覆盖）；\n【===================PBCom_RECCloseLog.txt ===================】\n保存着从此次运行工具开始，日志框显示过的所有数据（下次运行工具时将自动覆盖）；\n\n\n============================【综上】============================\n              感谢使用！【Spectre 2021年8月17日】".format(Path_LogDir,SystemTime_Str,LogSN)

    def __init__(self,UI) -> None:
        super().setupUi(UI)#@NOTE:继承父类的UI设置，即QT设计师可视化编程实现的UI设置,同时在父类的setupUI中创建了大量的变量，包括Box1这个变量

        #@NOTE:因为Dialog窗口默认使用的关闭和问号按钮作为窗口的操作按钮，所以这里重新设置窗口的按钮
        self.Main_UI=UI.setWindowFlags(Qt.WindowType.WindowMinMaxButtonsHint|Qt.WindowType.WindowCloseButtonHint)#@NOTE:此处的UI为窗口对象（QDialog、QWidget、QMainWindow等）

        #@NOTE:初始化禁用启用模块
        self.sBoxCom_Display()
        self.Button_CloseCOM.setEnabled(False)
        self.Button_Send.setEnabled(False)
        self.checkBox_AUTOSend.setEnabled(False)
        self.checkBox_Enter_N.setEnabled(False)
        self.checkBox_Enter_NR.setEnabled(False)

        #@NOTE:初始化模块值
        self.checkBox_Enter.setChecked(self.AtEndTheAutomaticPressEnter)#@NOTE:末尾添加回车
        self.checkBox_DisplaySend.setChecked(self.DisplaySend)#@NOTE:显示发送
        self.checkBox_ReceiveAutoEnter.setChecked(self.ReceiveAutoEnter_SW)#@NOTE:接收消息自动换行
        self.checkBox_DisplayEnterInfo.setChecked(self.DisplayEnterInfo_SW)
        self.checkBox_AUTOSend.setChecked(self.AUTOSend_SW)
        self.checkBox_DisplaySend_Date.setChecked(self.DisplayDate)
        self.checkBox_DisplaySend_Time.setChecked(self.DisplayTime)
        self.checkBox_DisplayNoEnterMSN.setChecked(self.DisplayNoEnterMSN_Index)
        self.Box_Baud.setCurrentText(self.Baud)
        if self.SelectNR_SW==1:
            self.checkBox_Enter_NR.setChecked(True)
        elif self.SelectNR_SW==0:
            self.checkBox_Enter_N.setChecked(True)
        self.spinBox_AUTOTime.setValue(self.AUTOSendTime)#@NOTE:自动发送频率
        self.spinBox_DisplayNoEnterMSNTime.setValue(self.ReceiveRefreshRate)#@NOTE:接收消息刷新速率
        self.Edit_CMD.setText(self.SendTextData)
        
            
        
        #@NOTE:将信号连接目标函数
        self.Button_OpenCOM.clicked.connect(self.sButton_OpenCom)
        self.Button_CloseCOM.clicked.connect(self.sButton_CloseCom)
        self.Button_Empty.clicked.connect(self.sButton_Empty)
        self.Button_Send.clicked.connect(self.sButton_Send)
        self.Button_RefreshCom.clicked.connect(self.sBoxCom_Display)
        self.Button_Save.clicked.connect(self.sButton_Save)
        self.Button_OpenLogDir.clicked.connect(self.sButton_OpenLogDir)
        #@NOTE:changed
        self.checkBox_Enter.stateChanged.connect(self.sCheck_Enter)
        self.checkBox_DisplaySend.stateChanged.connect(self.sCheck_DisplaySend)
        self.checkBox_AUTOSend.stateChanged.connect(self.sCheckAutoSend_ON)
        self.checkBox_AUTOSend.stateChanged.connect(self.sCheckAutoSend_OFF)
        self.checkBox_ReceiveAutoEnter.stateChanged.connect(self.sCheckReceivAutoEnter_SW)
        self.checkBox_DisplayEnterInfo.stateChanged.connect(self.sCheckDisplayEnterInfo_SW)
        self.checkBox_Enter_NR.stateChanged.connect(self.sCheckSelect_NR)
        self.checkBox_Enter_N.stateChanged.connect(self.sCheckSelect_N)
        self.checkBox_DisplaySend_Date.stateChanged.connect(self.sCheck_DisplayDateTime)
        self.checkBox_DisplaySend_Time.stateChanged.connect(self.sCheck_DisplayDateTime)
        self.checkBox_DisplayNoEnterMSN.stateChanged.connect(self.sCheck_DisplayNoEnterMSN)
        self.Browser_Display.textChanged.connect(self.sBrowserDisplayDataSave)#@NOTE:浏览区文本变动
        self.spinBox_AUTOTime.valueChanged.connect(self.sAutoSend)
        self.spinBox_DisplayNoEnterMSNTime.valueChanged.connect(self.sRefreshReceiveMsnFrequency)#@NOTE:接收消息刷新率改变则执行sRefreshReceiveMsnFrequency

        #@NOTE:自定义信号与槽的信号连接
        self.Receiv_Signal.signal.connect(self.sDisplay_Updata)#@TAG:非常重要！#@NOTE:因为子线程不能直接操作UI故需要使用自定义信号将接收到的信息（数据）通过发送信号的方式连接到主线程UI函数，然后主线程UI才能通过从信号一同发送过来的数据来更新显示页面
        # self.Refresh_Signal.signal.connect(self.sBoxCom_Display)#@NOTE:连接comdisplay函数

        #@NOTE:定时器连接发送函数
        self.Timer.timeout.connect(self.sButton_Send)#@NOTE:自动发送消息定时器：定时器到达设定时间就发送信号给发送函数
        self.Timer_AUTOCheckConnectCOM.timeout.connect(self.sAutoCheckConnectCom)#@NOTE:自动刷新已连接串口定时器：用于定时扫描已连接串口的定时器
        self.Timer_DisplayNoEnterMsn.timeout.connect(self.sRefreshReceiveMsn)#@NOTE:控制消息接收速率的定时器

        self.Browser_Display.ensureCursorVisible()#@NOTE:文本浏览区始终处于最新一条消息

        # QtCore.QObject.connectNotify()
        # self.Button_OpenCOM.clicked.connect()#@NOTE:该API在PYQT5往后的版本中已经被弃用
        self.Timer_AUTOCheckConnectCOM.start(1000)#@NOTE:开启自动扫描已经连接的串口，频率1次/S
        
        self.Browser_Display.append(self.DirectionsForUse_Str)#@NOTE:工具说明

    #@NOTE:用于保存设置数据到配置文件的函数
    def sSaveSettingConfig(self):

        self.SettingConfig_Dict={
            "EndAutoAddEnterIndex":self.AtEndTheAutomaticPressEnter,
            "DisplaySendIndex":self.DisplaySend,
            "ReceiveAutoEnterIndex":self.ReceiveAutoEnter_SW,
            "DisplayEnterInfoIndex":self.DisplayEnterInfo_SW,
            "Baud":self.Baud,
            "SelectNRIndex":self.SelectNR_SW,
            "AUTOSendIndex":self.AUTOSend_SW,
            "AUTOSendTime":self.AUTOSendTime,
            "SystemTime":self.SystemTime_Str,
            "LogSerialNumber":self.LogSN,
            "DisplayDate":self.DisplayDate,
            "DisplayTime":self.DisplayTime,
            "SendTextData":self.SendTextData,
            "DisplayNoEnterMSN_Index":self.DisplayNoEnterMSN_Index,
            "ReceiveRefreshRate":self.ReceiveRefreshRate,
            }#@NOTE:用于保存各项设置数据的字典

        #@NOTE:加载Pickle中的配置数据
        SettingConfig_Pickle_FD=open("PBCom_SettingConfig.pickle",'r+b')#@NOTE:打开用于储存标志位变量的文件
        try:
            SettingConfig_Pickle_Dict=pickle.load(SettingConfig_Pickle_FD)#@NOTE:加载Pickel数据到临时变量Dict中
        except:
            SettingConfig_Pickle_Dict=None
        SettingConfig_Pickle_FD.close()

        try:
            SettingConfig_Pickle_FD=open("PBCom_SettingConfig.pickle",'w+b')#@NOTE:打开用于储存标志位变量的文件
            pickle.dump(
            self.SettingConfig_Dict
            ,SettingConfig_Pickle_FD
            )
            SettingConfig_Pickle_FD.close()#@NOTE:写入完成关闭文件
            SettingConfig_Pickle_FD=None
        except:
            SettingConfig_Pickle_FD=None
            os.remove("PBCom_SettingConfig.pickle")
            SettingConfig_Pickle_FD=open("PBCom_SettingConfig.pickle",'w+b')#@NOTE:打开用于储存标志位变量的文件
            #@NOTE:将已经字典化的初始化变量数据写入Pickle文件中
            pickle.dump(
                self.SettingConfig_Dict
                ,SettingConfig_Pickle_FD
            )
            SettingConfig_Pickle_FD.close()#@NOTE:写入完成关闭文件
            SettingConfig_Pickle_FD=None
            
    #@NOTE:用于自动刷新串口列表的函数
    def sAutoCheckConnectCom(self):
        ComListTemp=self.Com.LookSerial()
        if self.ComList!=ComListTemp:
            self.ComList=ComListTemp
            self.Box_Com.clear()
            self.Box_Com.addItems(self.ComList)

    def sRefreshReceiveMsn(self):
        self.RefreshReceiveMsn_Index=1

    def sRefreshReceiveMsnFrequency(self):
        self.ReceiveRefreshRate=int(self.spinBox_DisplayNoEnterMSNTime.text())
        if 1==self.ComOpening_Index:    
            self.Timer_DisplayNoEnterMsn.start(self.ReceiveRefreshRate)
        self.sSaveSettingConfig()

    #@NOTE:显示COM口信息
    def sBoxCom_Display(self):
        self.ComList=None
        self.Box_Com.clear()
        self.ComList=self.Com.LookSerial()
        self.Box_Com.addItems(self.ComList)
        
    def sButton_OpenCom(self):
        if self.FirstOpenCom_index==1:
            self.Browser_Display.clear()
            self.FirstOpenCom_index=0
        self.Timer_AUTOCheckConnectCOM.stop()
        #@NOTE:串口选择模块禁用除选中外的其余串口
        COM_Name=self.Box_Com.currentText()#@NOTE:获取串口ID
        if COM_Name=='':
            self.sRefreshDateAndTimeInfo()
            self.Browser_Display.append("【警告】{}：你还没有选择串口！".format(self.DateTime_Str))
            self.Log+="【警告】{}：你还没有选择串口！\n".format(self.DateTime_Str)
            # COM_Name=self.Box_Com.setCurrentIndex(0)
        else:
            self.sRefreshDateAndTimeInfo()
            self.Browser_Display.append("[提示]{}：正在打开  ".format(self.DateTime_Str)+COM_Name+"……")
            self.Log+="[提示]{}：正在打开  ".format(self.DateTime_Str)+COM_Name+"……"
            
            Baud=self.Box_Baud.currentText()#@NOTE:获取串口波特率
            Stop=self.Box_Stop.currentText()#@NOTE:获取串口停止位
            Check=self.Box_Check.currentText()#@NOTE:获取串口校验位
            Data=self.Box_Data.currentText()#@NOTE:获取串口数据位


            self.serial=self.Com.OpenSerial(COM_Name,Baud,Stop,Check,Data)#@NOTE:按照设置参数打开串口

            if self.serial==False:
                if platform.system()=="Linux":
                    self.sRefreshDateAndTimeInfo()
                    self.Browser_Display.append("【警告】{}：串口".format(self.DateTime_Str)+COM_Name+"无法打开！\n【提示】：检测到程序运行的平台为Linux，请确认如下事项……\n      1.请确认串口是否被占用或是已经被拔出！\n      2.请确认程序是否拥有管理员权限！\n")
                    self.Log+="【警告】{}：串口".format(self.DateTime_Str)+COM_Name+"无法打开！\n【提示】：检测到程序运行的平台为Linux，请确认如下事项……\n      1.请确认串口是否被占用或是已经被拔出！\n      2.请确认程序是否拥有管理员权限！\n"
                else:
                    self.sRefreshDateAndTimeInfo()
                    self.Browser_Display.append("【警告】{}：串口".format(self.DateTime_Str)+COM_Name+"无法打开！")
                    self.Log+="【警告】{}：串口".format(self.DateTime_Str)+COM_Name+"无法打开！\n"
                    self.sRefreshDateAndTimeInfo()
                    self.Browser_Display.append("【提示】{}：检测到程序运行的平台为Windows，请确认串口是否被占用或是已经被拔出！\n".format(self.DateTime_Str))
                    self.Log+="【提示】{}：检测到程序运行的平台为Windows，请确认串口是否被占用或是已经被拔出！\n".format(self.DateTime_Str)
            else:
                self.ComOpening_Index=1
                self.sRefreshDateAndTimeInfo()
                self.Browser_Display.append("[提示]{}：".format(self.DateTime_Str)+COM_Name+"  已打开!\n")
                self.Log+="[提示]{}：".format(self.DateTime_Str)+COM_Name+"  已打开!\n"

                #@NOTE:将自动接收挂载到子线程
                self.StopRW=0
                self.ChildThread_Receiv=threading.Thread(target=self.sAutoReceiv)#@NOTE:启用多线程接收消息
                self.ChildThread_Receiv.start()#@NOTE:线程启动
                self.sRefreshReceiveMsnFrequency()#@NOTE:设置消息接收刷新率

                #@NOTE:锁定当前未选定的串口
                Count=self.Box_Com.count()
                Index=self.Box_Com.currentIndex()
                for i in range(Count):
                    if i != Index:
                        self.Box_Com.model().item(i).setEnabled(False)

                self.checkBox_AUTOSend.setEnabled(True)
                self.Button_CloseCOM.setEnabled(True)
                self.Button_OpenCOM.setEnabled(False)
                self.Box_Baud.setEnabled(False)
                self.Box_Data.setEnabled(False)
                self.Box_Check.setEnabled(False)
                self.Box_Stop.setEnabled(False)
                self.Button_Send.setEnabled(True)
                self.Button_RefreshCom.setEnabled(False)

            #@NOTE:打开后选项栏锁定
            #@NOTE:打开后打开按钮置灰，关闭按钮置亮
        
    def sButton_CloseCom(self):
        self.ComOpening_Index=0#@NOTE:串口打开标志位

        #@NOTE:解除串口选择锁定
        Count=self.Box_Com.count()
        for i in range(Count):
            self.Box_Com.model().item(i).setEnabled(True)

        self.Timer_DisplayNoEnterMsn.stop()#@NOTE:停止消息接收
        self.Timer_AUTOCheckConnectCOM.start(1000)#@NOTE:开启自动扫描已经连接的串口，频率1次/S
        
        #@NOTE:串口关闭后禁用的一揽子模块
        self.Button_Send.setEnabled(False)
        self.Button_CloseCOM.setEnabled(False)
        self.Button_OpenCOM.setEnabled(True)
        self.Box_Baud.setEnabled(True)
        self.Box_Data.setEnabled(True)
        self.Box_Check.setEnabled(True)
        self.Box_Stop.setEnabled(True)
        self.checkBox_AUTOSend.setEnabled(False)
        self.Button_RefreshCom.setEnabled(True)

        self.StopRW=1
        self.Timer.stop()
        self.sRefreshDateAndTimeInfo()
        self.Browser_Display.append("[提示]{}：正在关闭端口监听，请稍后……".format(self.DateTime_Str))
        self.Log+="[提示]{}：端口监听关闭成功，自动发送已禁用！\n".format(self.DateTime_Str)
        self.sRefreshDateAndTimeInfo()
        self.Browser_Display.append("[提示]{}：端口监听关闭成功，自动发送已禁用！".format(self.DateTime_Str))
        self.Log+="[提示]{}：正在关闭端口监听，请稍后……\n".format(self.DateTime_Str)

        try:
            self.sRefreshDateAndTimeInfo()
            self.Browser_Display.append("[提示]{}：正在关闭  (".format(self.DateTime_Str)+self.serial.name.upper()+")……")
            self.Log+="[提示]{}：正在关闭  (".format(self.DateTime_Str)+self.serial.name.upper()+")……\n"
            self.Com.CloseSerial()
            self.sRefreshDateAndTimeInfo()
            self.Browser_Display.append("[提示]{}：(".format(self.DateTime_Str)+self.serial.name.upper()+")  已关闭!")
            self.Log+="[提示]{}：(".format(self.DateTime_Str)+self.serial.name.upper()+")  已关闭!\n"
        except:
            print("[SUDO]:没有串口需要关闭!\n")

        if self.ChildThread_Receiv==1:
            self.sRefreshDateAndTimeInfo()
            self.Browser_Display.append("[提示]{}：正在关闭Com接收线程……".format(self.DateTime_Str))
            self.Log+="[提示]{}：正在关闭Com接收线程……\n".format(self.DateTime_Str)
            self.ChildThread_Receiv=0

    def sButton_OpenLogDir(self):
        if platform.system()=="Windows":
            # os.system("explorer.exe %s"%self.Path_LogDir)#@NOTE:使用os.sysytem调用explorer有黑框
            subprocess.run("explorer.exe %s"%self.Path_LogDir, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)#@NOTE:参考：https://blog.csdn.net/fancy10255/article/details/88965926
        elif platform.system()=="Linux":
            # os.system('xdg-open "%s"'%self.Path_LogDir)#@NOTE:与下面那句效果相同，妈的大佬牛逼！！！https://blog.csdn.net/weixin_39647499/article/details/110911800
            os.system("xdg-open %s"%self.Path_LogDir)

    def sButton_Save(self):
        self.SaveLog_Index=1
        self.sRefreshDateAndTimeInfo()
        self.Browser_Display.append("[提示]{}：Log文件已经保存至:".format(self.DateTime_Str)+self.Path_LogDir)#@NOTE:提示log路径
        self.Log+="[提示]{}：Log文件已经保存至:".format(self.DateTime_Str)+self.Path_LogDir
        self.sBrowserDisplayDataSave()

    def sBrowserDisplayDataSave(self):
        Log=self.Browser_Display.toPlainText()#@NOTE:参考写法http://www.cppcns.com/jiaoben/python/263015.html
        
        #@NOTE:关闭日志：自动检测CloseSaveLog的长度，超过10240则先写入log文件中
        if len(self.Log)>=10240:
            if self.CloseSaveLogSW_Index==2:
                self.sSaveBrowserToLog_Close()
            else:
                self.CloseSaveLogSW_Index=1
                self.sSaveBrowserToLog_Close()
        #@NOTE:手动保存日志
        if self.SaveLog_Index==1:
            self.sSaveBrowserLog(Log)
        Log=None

    def sSaveBrowserLog(self,xLog):
        LogFD=open(self.Path_LogDir+"/"+self.SystemTime_Str+"_PBCom_Log_"+str(self.LogSN)+".txt",'w+')
        self.LogSN+=1
        LogFD.write("{}".format(xLog))
        LogFD.close()
        LogFD=None
        self.SaveLog_Index=0
        self.sSaveSettingConfig()

    def sSaveBrowserToLog_Empty(self):
        RECEmptyLog=self.Browser_Display.toPlainText()
        RECEmptyLogFD=open(self.Path_EmptyLog,'w')
        RECEmptyLogFD.write(datetime.now().strftime("【数据清除时间】%Y年%m月%d日-%H:%M:%S\n")+"{}".format(RECEmptyLog))
        RECEmptyLogFD.close()
        RECEmptyLogFD=None

    def sSaveBrowserToLog_Close(self):
        if self.CloseSaveLogSW_Index==0:#@NOTE:程序退出时的保存
            RECCloseLog=self.Log
            self.Log=""
            RECCloseLogFD=open(self.Path_CloseLog,'a')
            # RECCloseLogFD.write(datetime.now().strftime("【工具关闭时间】%Y年%m月%d日-%H:%M:%S\n【日志记录字节】≈{}\n\n[===================感谢您的使用！——Spectre===================]\n\n".format(len(RECCloseLog)))+"{}".format(RECCloseLog))
            RECCloseLogFD.write("{}".format(RECCloseLog)+datetime.now().strftime("【工具关闭时间】%Y年%m月%d日-%H:%M:%S\n\n[===================感谢您的使用！——Spectre===================]\n\n"))
            RECCloseLogFD.close()
            RECCloseLogFD=None
            RECCloseLog=""
        elif self.CloseSaveLogSW_Index==1:#@NOTE:Log超过10240字节清空并写入到log文件
            RECCloseLog=self.Log
            self.Log=""
            RECCloseLogFD=open(self.Path_CloseLog,'a')
            RECCloseLogFD.write("{}".format(RECCloseLog)+"\n[===========================10240=========================]\n\n")
            RECCloseLogFD.close()
            RECCloseLogFD=None
            RECCloseLog=""
            self.CloseSaveLogSW_Index=0
        elif self.CloseSaveLogSW_Index==2:#@NOTE:Log超过10240字节且首次写入到log文件
            RECCloseLog=self.Log
            self.Log=""
            RECCloseLogFD=open(self.Path_CloseLog,'w')
            RECCloseLogFD.write("{}".format(RECCloseLog)+"\n[===========================10240=========================]\n\n")
            RECCloseLogFD.close()
            RECCloseLogFD=None
            RECCloseLog=""
            self.CloseSaveLogSW_Index=0

    def sSaveCMDToLog(self):
        self.SendTextData=self.Edit_CMD.toPlainText()
        self.sSaveSettingConfig()

    def sButton_Empty(self):
        self.sSaveBrowserToLog_Empty()
        self.Log+=datetime.now().strftime("\n【数据清除时间】%Y年%m月%d日-%H:%M:%S\n\n")
        self.Browser_Display.clear()

    def sButton_Send(self):
        Text_Send=None
        Text_Send=self.Edit_CMD.toPlainText()
        if len(Text_Send)==0:
            self.sRefreshDateAndTimeInfo()
            self.Browser_Display.append("[提示]{}：CMD键入区为空！".format(self.DateTime_Str))
            self.Log+="[提示]{}：CMD键入区为空！\n".format(self.DateTime_Str)
        else:
            if Text_Send.find('\n')!=-1 and self.SelectNR_SW==1:#@NOTE:如果选择发送/N/R则处理末尾添加的/N回车
                Text_Send=Text_Send.replace('\n','\n\r')
                self.Com.Com_SendMsn(Text_Send)
            elif Text_Send.find('\n\r')!=-1 and self.SelectNR_SW==0:#@NOTE:如果选择发送/N/R则处理末尾添加的/N回车
                Text_Send=Text_Send.replace('\n\r','\n')
                self.Com.Com_SendMsn(Text_Send)
            else:
                self.Com.Com_SendMsn(Text_Send)

            self.sCheck_DisplaySend()
            self.sCheck_Enter()
            
            if None!=self.Enter:#@NOTE:如果末尾添加回车打开
                self.Com.Com_SendMsn(Text_Send+self.Enter)

                #@NOTE:根据选择处理显示的回车信息
                if Text_Send.find('\n\r')!=-1:
                    Text_Send=Text_Send.replace('\n\r','  <|N|R回车>')
                elif Text_Send.find('\n')!=-1:
                    Text_Send=Text_Send.replace('\n','  <|N回车>')

                if self.DisplaySend:
                    if self.DisplayEnterInfo_SW==1:#@NOTE:如果打开显示enter信息
                        if self.SelectNR_SW==1:
                            self.sRefreshDateAndTimeInfo()
                            self.Browser_Display.append("[发送]{}：".format(self.DateTime_Str)+Text_Send+"  <|N|R回车>")
                            self.Log+="[发送]{}：".format(self.DateTime_Str)+Text_Send+"  <|N|R回车>\n"
                        else:
                            self.sRefreshDateAndTimeInfo()
                            self.Browser_Display.append("[发送]{}：".format(self.DateTime_Str)+Text_Send+"  <|N回车>")
                            self.Log+="[发送]{}：".format(self.DateTime_Str)+Text_Send+"  <|N回车>\n"
                    else:
                        self.sRefreshDateAndTimeInfo()
                        self.Browser_Display.append("[发送]{}：".format(self.DateTime_Str)+Text_Send)
                        self.Log+="[发送]{}：".format(self.DateTime_Str)+Text_Send+"\n"
            else:
                if self.DisplaySend:
                    if self.DisplayEnterInfo_SW==1:
                        #@NOTE:根据选择处理显示的回车信息
                        if Text_Send.find('\n\r')!=-1:
                            Text_Send=Text_Send.replace('\n\r','  <|N|R回车>')
                        elif Text_Send.find('\n')!=-1:
                            Text_Send=Text_Send.replace('\n','  <|N回车>')
                    self.sRefreshDateAndTimeInfo()
                    self.Browser_Display.append("[发送]{}：".format(self.DateTime_Str)+Text_Send)
                    self.Log+="[发送]{}：".format(self.DateTime_Str)+Text_Send+"\n"
      
    def sCheck_Enter(self):
        #@TODO:自动末尾添加回车反映到日志栏
        if self.checkBox_Enter.isChecked():
            self.AtEndTheAutomaticPressEnter=1
            self.checkBox_Enter_N.setEnabled(True)
            self.checkBox_Enter_NR.setEnabled(True)
            if self.checkBox_Enter_N.isCheckable():
                self.Enter='\n'
            else:
                self.Enter='\n\r'
        else:
            self.AtEndTheAutomaticPressEnter=0
            self.checkBox_Enter_N.setEnabled(False)
            self.checkBox_Enter_NR.setEnabled(False)
            self.Enter=None
        self.sSaveSettingConfig()

    def sCheck_DisplaySend(self):
        if self.checkBox_DisplaySend.isChecked():
            self.DisplaySend=1
        else:
            self.DisplaySend=0
        self.sSaveSettingConfig()
        
    def sCheck_DisplayDateTime(self):
        if self.checkBox_DisplaySend_Date.isChecked():
            self.DisplayDate=1
        else:
            self.DisplayDate=0
        if self.checkBox_DisplaySend_Time.isChecked():
            self.DisplayTime=1
        else:
            self.DisplayTime=0
        self.sSaveSettingConfig()

    def sRefreshDateAndTimeInfo(self):
        if self.DisplayDate==1 and self.DisplayTime!=1:
            self.DateTime_Str=datetime.now().strftime("[%Y年%m月%d日]")
        elif self.DisplayDate!=1 and self.DisplayTime==1:
            self.DateTime_Str=datetime.now().strftime("[%H:%M:%S]")
        elif self.DisplayDate==1 and self.DisplayTime==1:
            self.DateTime_Str=datetime.now().strftime("[%Y年%m月%d日-%H:%M:%S]")
        else:
            self.DateTime_Str=""

    def sAutoReceiv(self):
        Receiv="".encode()
        Receiv_item=None
        Receiv_List=[]

        while self.StopRW==0:
            if 1==self.RefreshReceiveMsn_Index:#@NOTE:控制接收速率的开关
                Receiv=self.Com.Com_Receiv()
                self.RefreshReceiveMsn_Index=0
            if Receiv==False:
                Receiv="【警告】{}:设备已被移除！！！".format(self.DateTime_Str)#@ALTER:V1.0.2.修复了时间漏显示的bug
                self.Receiv_Signal.sSend_Signal(Receiv)
                self.StopRW=1
            else:
                if self.ReceiveAutoEnter_SW==1:#@NOTE:接收消息自动换行
                    if Receiv.decode()!="":
                        Receiv_List.append(Receiv.decode())
                        i=len(Receiv_List)
                        Receiv_item="".join(Receiv_List)
                        Receiv="".encode()
                        if i!=0:#@NOTE:接收List非空
                            # I=Receiv_item.find('\r')
                            # print(I)#@NOTE:你大爷！！！返回值是-1！！！
                            if Receiv_item.find('\r')!=-1:#@ALTER:V1.0.1.修复接收到的数据如果没有回车则不会显示2.2021年8月13日：增加显示无回车消息标志位，用于手动选择区分正常长指令与异常无回车消息
                                self.Receiv_Signal.sSend_Signal(Receiv_item)#@NOTE:将消息通过信号发射器发送到display函数上去
                                Receiv_List=[]
                                Receiv_item=None
                            elif 1==self.DisplayNoEnterMSN_Index:#@ALTER:V1.0.1.修复接收到的数据如果没有回车则不会显示；2.2021年8月13日：增加显示无回车消息标志位，用于手动选择区分正常长指令与异常无回车消息
                                if 10240>len(Receiv_item):
                                    self.Receiv_Signal.sSend_Signal(("[消息累积中]"+Receiv_item))#@NOTE:将消息通过信号发射器发送到display函数上去
                                else:
                                    self.Receiv_Signal.sSend_Signal(("[累积超过10240字节触发自动换行]"+Receiv_item))#@NOTE:将消息通过信号发射器发送到display函数上去
                                    Receiv_List=[]
                                    Receiv_item=None

                else:
                    self.Receiv_Signal.sSend_Signal(Receiv.decode())
                

        if self.StopRW==1:
            self.checkBox_AUTOSend.setChecked(False)
            self.checkBox_AUTOSend.setEnabled(False)
            self.StopRW=2
            #@TAG:重要-多线程回收（字越少越重要！！）
            self.ChildThread_Receiv=self.stop_thread(self.ChildThread_Receiv)
            
    def sDisplay_Updata(self,Receiv):
        # Receiv=Receiv.decode()
        Enter_Temp="  <"
        self.sRefreshDateAndTimeInfo()
        if Receiv.find(":设备已被移除！！！"):#@ALTER:V1.0.2.修复了因为条件不符合导致的串口无法自动关闭的bug
            self.Browser_Display.append(Receiv)
            self.Log+=Receiv+"\n"
            self.sButton_CloseCom()
        else:
            if len(Receiv)!=0:
                if self.DisplayEnterInfo_SW==1:
                    Receiv=Receiv.replace('\r',"  <|R回车>")
                    Receiv=Receiv.replace('\n',"  <|N回车>")
                    Receiv=Receiv.replace("  <|R回车>  <|N回车>","  <|R|N回车>")
                    if -1!=Receiv.find("  <|R回车>") or -1!=Receiv.find("  <|N回车>") or -1!=Receiv.find("  <|R|N回车>"):
                        Enter_Temp=""#@ALTER:V1.0.1.增加接收到的数据如果没有回车则显示无换行符
                    else:
                        Enter_Temp="  <无换行符>"
                    self.sRefreshDateAndTimeInfo()
                    self.Browser_Display.append("[接收]{}：".format(self.DateTime_Str)+Receiv+Enter_Temp)
                    self.Log+="[接收]{}：".format(self.DateTime_Str)+Receiv+Enter_Temp+"\n"
                else:
                    self.sRefreshDateAndTimeInfo()
                    self.Browser_Display.append("[接收]{}：".format(self.DateTime_Str)+Receiv)
                    self.Log+="[接收]{}：".format(self.DateTime_Str)+Receiv+"\n"

    def sCheckAutoSend_ON(self):
        if True==self.checkBox_AUTOSend.isChecked():
            self.AUTOSend_SW=1
            self.sAutoSend()
        self.sSaveSettingConfig()

    def sAutoSend(self):
        self.AUTOSendTime=int(self.spinBox_AUTOTime.text())
        if self.AUTOSend_SW==1:
            self.Timer.start(self.AUTOSendTime)
        self.sSaveSettingConfig()

    def sCheck_DisplayNoEnterMSN(self):
        if self.checkBox_DisplayNoEnterMSN.isChecked():
            self.DisplayNoEnterMSN_Index=1
        else:
            self.DisplayNoEnterMSN_Index=0
        self.sSaveSettingConfig()

    def sCheckAutoSend_OFF(self):
        if True!=self.checkBox_AUTOSend.isChecked():
            self.Timer.stop()
            self.AUTOSend_SW=0
        self.sSaveSettingConfig()

    def sCheckReceivAutoEnter_SW(self):
        if True == self.checkBox_ReceiveAutoEnter.isChecked():
            self.ReceiveAutoEnter_SW=1
        elif False == self.checkBox_ReceiveAutoEnter.isChecked():
            self.ReceiveAutoEnter_SW=0
        self.sSaveSettingConfig()

    def sCheckDisplayEnterInfo_SW(self):
        if True == self.checkBox_DisplayEnterInfo.isChecked():
            self.DisplayEnterInfo_SW=1
        elif False == self.checkBox_DisplayEnterInfo.isChecked():
            self.DisplayEnterInfo_SW=0
        self.sSaveSettingConfig()

    def sCheckSelect_NR(self):
        if True == self.checkBox_Enter_NR.isChecked():
            self.SelectNR_SW=1#@NOTE:|R|N
            self.checkBox_Enter_N.setChecked(False)#@NOTE:去除|N的勾选
            self.Enter="\n\r"
        elif False == self.checkBox_Enter_NR.isChecked():
            self.SelectNR_SW=0#@NOTE:|N
            self.checkBox_Enter_N.setChecked(True)#@NOTE:勾选|N
            self.Enter="\n"
        self.sSaveSettingConfig()

    def sCheckSelect_N(self):
        if True == self.checkBox_Enter_N.isChecked():
            self.SelectNR_SW=0#@NOTE:|N
            self.checkBox_Enter_NR.setChecked(False)#@NOTE:去除|N|R的勾选
            self.Enter="\n"
        elif False == self.checkBox_Enter_N.isChecked():
            self.SelectNR_SW=1#@NOTE:|N|R
            self.checkBox_Enter_NR.setChecked(True)#@NOTE:勾选|N|R
            self.Enter="\n\r"
        self.sSaveSettingConfig()

    #@TAG:重要！！！参考：
    # https://blog.csdn.net/qq_15181569/article/details/93299164
    # https://blog.csdn.net/qq_41082014/article/details/92085252?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EsearchFromBaidu%7Edefault-1.pc_relevant_baidujshouduan&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EsearchFromBaidu%7Edefault-1.pc_relevant_baidujshouduan
    #@NOTE:用于回收线程的函数1
    def _async_raise(self,tid, exctype):
        """raises the exception, performs cleanup if needed"""        
        tid = ctypes.c_long(tid)
        if not inspect.isclass(exctype):
            exctype = type(exctype)
        self.ChildThread_Receiv=1#@NOTE:正在关闭Com线程
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("invalid thread id")#@NOTE:当程序出现错误，python会自动引发异常，也可以通过raise显示引发异常处。一旦执行了raise语句，raise后面的语句将不能执行。https://www.cnblogs.com/zhangyin6985/p/7229553.html
        elif res != 1:
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")
        

    #@NOTE:用于回收线程的函数2
    def stop_thread(self,thread):
        self._async_raise(thread.ident, SystemExit)